defmodule Electric.StackConfig do
  use GenServer

  def put(stack_id, key, val) do
    :ets.insert(table(stack_id), {key, val})
  end

  def lookup(stack_id, key, default \\ nil) do
    :ets.lookup_element(table(stack_id), key, 2, default)
  end

  def spawn_opts(stack_id, process_name) do
    stack_id
    |> lookup(:process_spawn_opts, %{})
    |> Map.get(process_name, [])
  end

  def lookup!(stack_id, key) do
    :ets.lookup_element(table(stack_id), key, 2)
  rescue
    ArgumentError ->
      raise RuntimeError,
        message: "stack config value #{inspect(key)} is missing for stack #{stack_id}"
  end

  @doc false
  # Should provide all required values not defined dynamically at stack init
  def default_seed_config do
    [
      snapshot_timeout_to_first_data: :timer.seconds(30),
      shape_hibernate_after: Electric.Config.default(:shape_hibernate_after),
      shape_enable_suspend?: Electric.Config.default(:shape_enable_suspend?),
      chunk_bytes_threshold: Electric.ShapeCache.LogChunker.default_chunk_size_threshold(),
      feature_flags: [],
      process_spawn_opts: %{}
    ]
  end

  ###

  def name(stack_ref) do
    Electric.ProcessRegistry.name(stack_ref, __MODULE__)
  end

  def table(stack_id) do
    :"#{inspect(__MODULE__)}:#{stack_id}"
  end

  ###

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: name(opts))
  end

  @impl GenServer
  def init(opts) do
    stack_id = Keyword.fetch!(opts, :stack_id)

    seed_config = Keyword.merge(default_seed_config(), Keyword.get(opts, :seed_config, []))

    tab = table(stack_id)
    :ets.new(tab, [:public, :named_table, :set, read_concurrency: true])
    :ets.insert(tab, seed_config)

    {:ok, nil}
  end
end
